/*
 * Copyright (C) 2024, Ingenic Semiconductor Co.,Ltd.
 * Author: Keven <keven.ywhan@ingenic.com>
 */

#include <common.h>
#include <ccu.h>
#include <spl.h>
#include <clk.h>
#include <sram.h>
#include <msc.h>
#include <sfc.h>
#include <usb.h>

void hang(void)
{
	while(1);
}


void spl_mmc_load_image(struct spl_image_info *spl_image)
{
	unsigned int boot_mode;
	unsigned int image_size_sectors;
	unsigned int image_offset_sectors;

	spl_image->size = CONFIG_SYS_MAX_SIZE;
	spl_image->load_addr = CONFIG_SYS_TEXT_BASE;
	spl_image->entry_point = CONFIG_SYS_TEXT_BASE + 0x80;

	sd_init();
	boot_mode = spl_boot_mode();
	if(boot_mode == MMCSD_MODE_RAW) {
		image_size_sectors = (spl_image->size + MSC_BLOCK_SIZE - 1) / MSC_BLOCK_SIZE;
		image_offset_sectors = CONFIG_SYS_OFFSET / MSC_BLOCK_SIZE;
		sd_block_read(image_offset_sectors, image_size_sectors, (void *)spl_image->load_addr);
		flush_l1cache_all();
		serial_puts("image load addr:");
		serial_puthex(spl_image->load_addr);

		/* unsigned int *p = (unsigned int *)spl_image->load_addr; */
		/* int i; */
		/* for(i = 0 ; i < 0x100; i = i+4) { */
			/* serial_puthex(spl_image->load_addr + 4*i); */
			/* serial_puts(": "); */
			/* serial_puthex(p[i + 0]); */
			/* serial_puthex(p[i + 1]); */
			/* serial_puthex(p[i + 2]); */
			/* serial_puthex(p[i + 3]); */
		/* } */
	} else if (boot_mode == MMCSD_MODE_FAT) {

	} else {
		serial_puts("spl: wrong MMC boot mode\n");
		hang();
	}
}

void spl_nor_load_image(struct spl_image_info *spl_image)
{

	spl_image->size = CONFIG_SYS_MAX_SIZE;
	spl_image->load_addr = CONFIG_SYS_TEXT_BASE;
	spl_image->entry_point = CONFIG_SYS_TEXT_BASE + 0x80;

	sfc_init();

}
void spl_nand_load_image(struct spl_image_info *spl_image)
{
	sfc_init();
}
void spl_uart_load_image(struct spl_image_info *spl_image)
{
}
void spl_usb_load_image(struct spl_image_info *spl_image)
{
	usb_init();
}
void spl_xip_load_image(struct spl_image_info *spl_image)
{
}

int spl_load_image(struct spl_image_info *spl_image)
{
	int ret = 0;
	unsigned int boot_device;

	boot_device = spl_boot_device();
	switch(boot_device) {
	case BOOT_DEVICE_MMC:
		spl_mmc_load_image(spl_image);
		break;
	case BOOT_DEVICE_SFC_NOR:
		spl_nor_load_image(spl_image);
		break;
	case BOOT_DEVICE_SFC_NAND:
		spl_nand_load_image(spl_image);
		break;
	case BOOT_DEVICE_UART:
		spl_uart_load_image(spl_image);
		break;
	case BOOT_DEVICE_USB:
		spl_usb_load_image(spl_image);
		break;
	case BOOT_DEVICE_XIP:
		spl_xip_load_image(spl_image);
		break;
	default:
		serial_puts("Error! unknown boot device\n");
		hang();
	}

	return ret;
}

extern void *memcpy(void * aa, const void *bb, long unsigned int n);
extern char *main_hart_deal_end;
extern char *main_hart_deal_start;
extern char *main_hart_deal;
void jump_to_image(struct spl_image_info *spl_image)
{
	typedef void (*image_entry_t)(void);
	image_entry_t image_entry = (image_entry_t) (spl_image->entry_point);

	serial_puts("image entry point:");
	serial_puthex(spl_image->entry_point);
	image_entry();


#if 0
	/* enable l2cache after jump to ddr */
	unsigned long main_hart_deal_clen = (unsigned long)(&main_hart_deal_end) - (unsigned long)(&main_hart_deal_start);
	unsigned int hartmapval = 1;
	unsigned int l2cache_maxsize = get_l2cache_maxsize();
	void *l2cache_setaddr = (void *)(spl_image->load_addr + spl_image->size + 4096);
	unsigned long prepare_jump_nocache;

	memcpy(l2cache_setaddr, &main_hart_deal, main_hart_deal_clen);
	prepare_jump_nocache = virt_to_phys(l2cache_setaddr);

	set_reset_entry(spl_image->entry_point);
	flush_l1cache_all();

	serial_puts("enable l2cache entry point:");
	serial_puthex((unsigned int)l2cache_setaddr);
	serial_puts("image entry point:");
	serial_puthex(spl_image->entry_point);
	__asm__ __volatile__(
			"mv a0, %0\t\n"
			"mv a1, %1\t\n"
			"mv a2, %2\t\n"
			"mv a3, %3\t\n"
			"jr a3\t\n"
			::"r"(l2cache_maxsize),
			"r"(hartmapval),
			"r"(spl_image->entry_point),
			"r"(prepare_jump_nocache)
			:"a0", "a1", "a2", "a3"
			);
	__builtin_unreachable();
#endif

}

void handle_trap(void)
{
	serial_puts("Trap happen!\n");
}

int spl_main(int argc, char *argv[])
{
	int ret = 0;
	struct spl_image_info spl_image = {0};

#ifndef CONFIG_FPGA_TEST
	clk_init();
#endif
	serial_init();
	serial_puts("\n");
	serial_puts("serial init success!\n");
	ram_init();

	spl_load_image(&spl_image);
	jump_to_image(&spl_image);

	return ret;
}

